﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;

using sfsocket.corelib.connection.client;
using sfsocket.corelib.message;

namespace sfsocket.corelib.connection.server
{
    public class ChatServerConnection
    {
         // all connections connected to this server
        public static Hashtable m_AllConnections = new Hashtable();
        protected TcpClient m_TcpClient;
        protected IPEndPoint m_ClientIP;
        protected IPEndPoint m_RemoteIP;
        protected bool m_IsConnected = false;

        // constants
        private const int DEFAULT_LENGTH_BYTES = 4;

        //---------------------------------------------------------------------
        // Events
        //---------------------------------------------------------------------
        public event EventHandler OnConnected;
        public event EventHandler OnDisconnecting;
        public event EventHandler OnDisconnected;
        public event EventHandler OnConnectionError;

        //---------------------------------------------------------------------
        //  
        //---------------------------------------------------------------------
        // property
        public bool IsConnected
        {
            get { return m_IsConnected; }
            set { m_IsConnected = value; }
        }

        public IPEndPoint ClientIP
        {
            get { return m_ClientIP; }
            set { m_ClientIP = value; }
        }

        //---------------------------------------------------------------------
        //  constructors 
        //---------------------------------------------------------------------
        public ChatServerConnection()
        {
        }

        /// <summary>
        ///  construct in server
        /// </summary>
        public ChatServerConnection(TcpClient _client)
        {
            m_TcpClient = _client;
            _client.ReceiveBufferSize = 256 * 1024;

            if (m_TcpClient.Connected)
            {
                if (OnConnected!=null)
                    OnConnected(this, EventArgs.Empty);
                
                TraceMsg("Accept Msg from " + m_TcpClient.Client.RemoteEndPoint.ToString());
                m_IsConnected = true;
                m_ClientIP = (IPEndPoint)_client.Client.RemoteEndPoint;
                lock(m_AllConnections)
                {
                    m_AllConnections.Add(m_TcpClient.Client.RemoteEndPoint.ToString(), this);
                }

                // read length field 
                ChatClientState cs = new ChatClientState(GetStream(), new byte[DEFAULT_LENGTH_BYTES]);
                IAsyncResult result = GetStream().BeginRead(
                        cs.ByteBuffer, cs.TotalBytes, cs.ByteBuffer.Length - cs.TotalBytes,
                        new AsyncCallback(ReceivedLengthCallback), cs);
            }
        }

        //---------------------------------------------------------------------
        // methods
        //---------------------------------------------------------------------
        public virtual void Disconnect()
        {
            if (m_TcpClient.Connected)
            {
                m_TcpClient.Close();
                if (OnDisconnected != null)
                {
                    OnDisconnected(this, EventArgs.Empty);
                }
            }

            Console.WriteLine("[{0,3}] - {1} : Disconnect",
                Thread.CurrentThread.ManagedThreadId,
                DateTime.Now.ToString("HH:mm:ss.ffff"));
        }


        public NetworkStream GetStream()
        {
            if (m_TcpClient != null)
            {
                return m_TcpClient.GetStream();
            }
            return null;
        }

        //---------------------------------------------------------------------
        // Asyc operations 
        //---------------------------------------------------------------------
        /// <summary>
        /// header: length + type 
        /// </summary>
        private void ReceivedLengthCallback(IAsyncResult iar)
        {
            try
            {
                ChatClientState cs = (ChatClientState)iar.AsyncState;
                int bytesRead = GetStream().EndRead(iar);
                cs.AddToTotalBytes(bytesRead);

                // if client has disconnected 
                if (bytesRead < 1)
                {
                    lock(m_AllConnections) {
                        m_AllConnections.Remove(m_TcpClient.ToString());
                    }
                    TraceMsg("Disconnect " + m_TcpClient.Client.LocalEndPoint.ToString());
                    return;
                }

                // recevie 4 byte length completed, expand msg buffer size
                if (cs.TotalBytes == cs.ByteBuffer.Length)
                {
                    int msg_len = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(cs.ByteBuffer, 0));
                    // expand byte buffer 
                    byte[] msg_buff = new byte[msg_len + DEFAULT_LENGTH_BYTES];
                    Array.Copy(cs.ByteBuffer, msg_buff, bytesRead);
                    cs.ByteBuffer = msg_buff;
                }

                IAsyncResult result = cs.NetStream.BeginRead(
                            cs.ByteBuffer, cs.TotalBytes, cs.ByteBuffer.Length - cs.TotalBytes,
                            new AsyncCallback(ReceivedBodyCallback), cs);

            }
            catch (Exception ex)
            {
                lock (m_AllConnections)
                {
                    m_AllConnections.Remove(m_TcpClient.Client.RemoteEndPoint.ToString());
                }
                TraceMsg("Disconnect " + m_TcpClient.Client.RemoteEndPoint.ToString() + 
                    " due to " + ex.Message);
                return;
            }
        }

        /// <summary>
        /// receive body
        /// </summary>
        private void ReceivedBodyCallback(IAsyncResult iar)
        {
            try
            {


                ChatClientState cs = (ChatClientState)iar.AsyncState;
                int bytesRead;

                bytesRead = cs.NetStream.EndRead(iar);
                cs.AddToTotalBytes(bytesRead);
                TraceMsg("Receive bytes: " + bytesRead);

                // if client has disconnected
                if (bytesRead < 1)
                {
                    lock (m_AllConnections)
                    {
                        m_AllConnections.Remove(m_TcpClient.ToString());
                        TraceMsg("Disconnect " + m_TcpClient.ToString());
                    }
                    return;
                }

                // receive body completed
                if (cs.TotalBytes == cs.ByteBuffer.Length)
                {
                    BaseMessage msg = DecodeMessage(cs.ByteBuffer, 0, cs.ByteBuffer.Length);
                    TraceMsg("Receive Message" + Enum.GetName(typeof(MessageTypeEnum), msg.MessageType));
                }

                // if not receive completed
                else if (cs.TotalBytes < cs.ByteBuffer.Length)
                {
                    TraceMsg("need to receive more bytes");
                    cs.NetStream.BeginRead(cs.ByteBuffer, cs.TotalBytes,
                        cs.ByteBuffer.Length - cs.TotalBytes,
                        new AsyncCallback(ReceivedBodyCallback), cs);
                }

                // continue reading next header from client
                cs = new ChatClientState(GetStream(), new byte[DEFAULT_LENGTH_BYTES]);
                IAsyncResult result = GetStream().BeginRead(
                        cs.ByteBuffer, cs.TotalBytes, cs.ByteBuffer.Length - cs.TotalBytes,
                        new AsyncCallback(ReceivedLengthCallback), cs);
            }
            catch (Exception ex)
            {
                lock (m_AllConnections)
                {
                    m_AllConnections.Remove(m_TcpClient.Client.RemoteEndPoint.ToString());
                    TraceMsg("Disconnect " + m_TcpClient.Client.RemoteEndPoint.ToString() +
                        "due to " + ex.Message);

                }
                return;
            }
        }

        //---------------------------------------------------------------------
        // Message operations 
        //---------------------------------------------------------------------
        private BaseMessage DecodeMessage(byte[] msgbuff, int offset, int len)
        {
            int msg_type = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(msgbuff, 4));
            BaseMessage msg = null;
            switch (msg_type)
            {
                case (int)MessageTypeEnum.PING:
                    msg = PingMessage.Parse(msgbuff);
                    OnPing(msg as PingMessage);
                    break;
                case (int)MessageTypeEnum.FILEDATE:
                    msg = FileDataMessage.Parse(msgbuff);
                    if (msg != null)
                    {
                        //fmsg.Dump();
                        OnFileDataReceive(msg as FileDataMessage);
                    }
                    break;
                case (int) MessageTypeEnum.BROADCAST:
                    msg = ChatMessage.Parse(msgbuff);
                    TraceMsg("Receving broadcast message : " + ((ChatMessage)msg).Chatmsg);
                    //msg.DumpHex();
                    BroadcastMsg(msg);
                defalut:
                    break;
            }
            return msg;
        }


        public int SendMsg(BaseMessage msg)
        {
            try
            {
                lock (m_TcpClient.GetStream())
                {
                    NetworkStream ns = GetStream();
                    ns.Write(msg.GetBytes(), 0, msg.GetBytes().Length);
                    ns.Flush();
                    // TraceMsg("Sending " + Enum.GetName(typeof(MessageTypeEnum), msg.MessageType) + " msg");
                    return 1;
                }

            }
            catch (Exception ex)
            {
                throw ex;
            }

        }

        public void BroadcastMsg(BaseMessage msg)
        {
            //TraceMsg("Broadcasting " + Enum.GetName(typeof(MessageTypeEnum), msg.MessageType) + " msg : ");
            lock (m_AllConnections)
            {
                
                foreach (DictionaryEntry dictEntry in m_AllConnections)
                {
                    ChatServerConnection con = (ChatServerConnection)(dictEntry.Value);
                    TraceMsg("BC to : " + con.ClientIP.ToString());
                    NetworkStream ns = con.GetStream();
                    ns.Write(msg.GetBytes(), 0, msg.GetBytes().Length);
                    ns.Flush();
                    // TraceMsg("Length = " + msg.GetBytes().Length);
                    // TraceMsg("Sending message to " + ((ChatServerConnection)(con.Value)).ClientIP.ToString());
                }
            
            }


        }

        private void OnPing(PingMessage msg)
        {
            TraceMsg("receiving ping msg, send pong msg");
            PongMessage pong = new PongMessage();
            SendMsg(pong);
        }

        private void OnFileDataReceive(FileDataMessage msg)
        {
            TraceMsg("handle filedata msg");
            try
            {
                // 3) read file data and save to local file system
                FileStream fs = new FileStream(msg.FileName + ".new", FileMode.Create, FileAccess.Write);
                if (fs.CanWrite)
                {
                    fs.Write(msg.FileData, 0, msg.FileData.Length);
                }
                fs.Close();
                string x = String.Format("Write data to file : {0} with size {1} Successfully!",
                    msg.FileName, msg.FileData.Length);
                TraceMsg(x);


            }
            catch (Exception ex)
            {
                TraceMsg(ex.Message);
                throw ex;
            }
        }

        public void TraceMsg(string msg)
        {
            Console.WriteLine("[{0,3}] - {1} : {2}",
                    Thread.CurrentThread.ManagedThreadId,
                    DateTime.Now.ToString("HH:mm:ss.ffff"), msg);
        }
    }
}
